﻿
/**
 * @Licence
 * This component is free for use, modification and distribution under the following condition:
 * Just mention the name and the url of the author in a part of your product that is visible to the user (about section, documentation...)
 * 
 * EnhancedButton
 * This class extends the Button class.
 * It provides additional properties and styles to set the position of the icon inside the button.
 * The rendering is also enhanced by adding the hability of applying any filter on the icon at the different states of the button (at up, over, down states...)
 * 
 * Depending on the position of the icon and the labelPlacement property, the icon will move when the button is resized
 * or will remain always at the same place, regardless of the length of the label for example. This will have to be taken
 * into account while styling an application.
 * 
 * v 1.0
 * 	- the icon is free
 * 
 * Known issues:
 * 1- Rotation
 * The rotation, rotationX, Y and Z are not used as they lead to unexpected behavior. It seems this is a known issue in the Flex framework.
 * So avoid the usage of the Rotation filter.
 * 
 * @author Arnaud FOUCAL (afoucal@free.fr - http://afoucal.free.fr) - Defender of oppressed icons
 */

 
package util 
{
	
	import mx.controls.Button;
	import flash.filters.BitmapFilter;
	import flash.filters.ColorMatrixFilter;
	import flash.events.Event;


	/**
	 * The horizontal position in pixels of the icon
	 * 
	 * @default 0
	 */
	[Style(name = "iconPosX", type = "Number",  format="Length", inherit = "no")]
	
	/**
	 * The vertical position of the icon
	 * 
	 * @default 0
	 */
	[Style(name = "iconPosY", type = "Number",  format="Length", inherit = "no")]
	
	/**
	 * The alpha of the icon
	 * 
	 * @default 0
	 */
	[Style(name = "iconAlpha", type = "Number", inherit = "no")]
	
	/**
	 * The array of filters for the upIcon
	 * 
	 * @default null
	 */
	[Style(name = "upIconFilters", type = "Array", format = "String", inherit = "no")]
	
	/**
	 * The array of filters for the downIcon
	 * 
	 * @default null
	 */
	[Style(name = "downIconFilters", type = "Array", format = "String", inherit = "no")]	
	
	/**
	 * The array of filters for the overIcon
	 * 
	 * @default null
	 */
	[Style(name = "overIconFilters", type = "Array", format = "String", inherit = "no")]
	
	/**
	 * The array of filters for the selectedUpIcon
	 * 
	 * @default null
	 */
	[Style(name = "selectedUpIconFilters", type = "Array", format = "String", inherit = "no")]
	
	/**
	 * The array of filters for the selectedDownIcon
	 * 
	 * @default null
	 */
	[Style(name = "selectedDownIconFilters", type = "Array", format = "String", inherit = "no")]	
	
	/**
	 * The array of filters for the selectedOverIcon
	 * 
	 * @default null
	 */
	[Style(name = "selectedOverIconFilters", type = "Array", format = "String", inherit = "no")]

	/**
	 * The array of filters for the disabled/disabledSelectedIcon
	 * 
	 * @default null
	 */
	[Style(name = "disabledIconFilters", type = "Array", format = "String", inherit = "no")]
	
	
	public class EnhancedButton extends MultilineButton
	{
		
		public function EnhancedButton() 
		{
			super();
		}
		
		/**
		 * Check CSS declarations and assigns values to corresponding properties
		 */
		private function getCSS() :void
		{
			/* Got an error on the 2nd line.
			 * Couldn't fixed it so other solution
			var cssStyle:CSSStyleDeclaration = StyleManager.getStyleDeclaration("." + styleName);
			var obj:Object = new cssStyle.defaultFactory();
			*/
			
			// Groups all new syles defined by the extension
			var newStyleList:Array = ['iconPosX', 'iconPosY', 'iconAlpha'];
			var newFilterStyleList:Array =
				[
				'upIconFilters', 'downIconFilters', 'overIconFilters', 'disabledIconFilters', 
				'selectedUpIconFilters', 'selectedDownIconFilters', 'selectedOverIconFilters'
				];
			
			// Loops on new styles to associate the value defined in the CSS and the corresponding property
			// Deals with non array CSS attributes
			for ( var i:Number = 0; i < newStyleList.length ; i++ )
			{
				if ( getStyle(newStyleList[i]) != undefined )
				{
					this["_" + newStyleList[i]] = getStyle(newStyleList[i]);
				}
			}
			
			// Deals with array CSS attributes
			for ( i = 0 ; i < newFilterStyleList.length ; i++ )
			{
				if ( getStyle(newFilterStyleList[i]) != undefined )
				{
					var styleType:* = typeof( getStyle(newFilterStyleList[i]) );
					var arrayStyle:Array = new Array();

					// a 'object' value means that the style is an array (more than 1 value in the CSS)
					if ( styleType == 'object' )
					{
						arrayStyle = getStyle(newFilterStyleList[i]);
						var filterArray:Array = new Array();

						// Loop on the css value and push the corresponding filters, taken from the parent application
						for (var f:Number = 0 ; f < arrayStyle.length ; f++)
						{
							filterArray.push(this.parentApplication[arrayStyle[f]]);
						}
						this["_" + newFilterStyleList[i]] = filterArray;
					}
					// a non 'object' value means that the CSS has only one filter that is actually processed as a string
					else
					{
						arrayStyle.push( this.parentApplication[getStyle(newFilterStyleList[i])]);
						this["_" + newFilterStyleList[i]] = arrayStyle;
					}
				}
			}
		}

		
		/*==================================================================
			COORDINATES AND POSITIONS + ROTATION
		==================================================================*/
		
		public const RELATIVE_COORDINATES:String = "relative";
		public const ABSOLUTE_COORDINATES:String = "absolute";
		
		/**
		 * System of coordinates in the button area :
		 * - absolute : based on the top left corner of the button, regardless of the labelPlacement property
		 * - relative : based on the default position of the icon, according to the labelPlacement property
		 * 
		 * @default relative
		 */
		[Inspectable(enumeration="absolute,relative", defaultValue="relative")]
		private var _coordinates:String = RELATIVE_COORDINATES;
		public function get coordinates() :String
		{
			return _coordinates;
		}
		public function set coordinates(c:String) :void
		{
			_coordinates = c;
			invalidateDisplayList();
		}
		
		/**
		 * Defines the horizontal position of the icon.
		 * The result depends on the value of the coordinate property.
		 * In a relative system, the iconPosX represents the horizontal offset in pixel from the default position, when the icon is placed in the button.
		 * In an absolute system, the iconPosX represents the distance in pixel from the left border of the button.
		 * 
		 * @default 0
		 */
		private var _iconPosX:Number = 0;
		public function get iconPosX() :Number
		{
			return _iconPosX;
		}
		public function set iconPosX(n:Number) :void
		{
			_iconPosX = n;
			invalidateDisplayList();
		}
		
		/**
		 * Defines the vertical position of the icon.
		 * The result depends on the value of the coordinate property.
		 * In a relative system, the iconPosY represents the vertical offset in pixel from the default position, when the icon is placed in the button
		 * In an absolute system, the iconPosY represents the distance in pixel from the upper border of the button.
		 * 
		 * @default 0
		 */
		private var _iconPosY:Number = 0;
		public function get iconPosY() :Number
		{
			return _iconPosY;
		}
		public function set iconPosY(n:Number) :void
		{
			_iconPosY = n;
			invalidateDisplayList();
		}
		
		/**
		 * The alpha to be applied on the icon
		 * 
		 * @default 1
		 */
		private var _iconAlpha:Number = 1;
		public function get iconAlpha() :Number
		{
			return _iconAlpha;
		}
		public function set iconAlpha(n:Number) :void
		{
			_iconAlpha = n;
			invalidateDisplayList();
		}
		
		private var iconChildren:Array = [
			'upIcon','downIcon','overIcon','disabledIcon',
			'selectedUpIcon', 'selectedDownIcon', 'selectedOverIcon', 'selectedDisabledIcon'];
			
		/**
		 * Moves the icon horizontally given the dx value and vertically given the dy value
		 * 
		 * @param	dx Number
		 * @param	dy Number
		 */
		private function moveIconBy( dx:Number, dy:Number ) :void
		{
			switch (_coordinates)
			{
				case RELATIVE_COORDINATES:
					// Loop on all icons to change the x and y
					for ( var i:Number = 0; i < iconChildren.length ; i++ )
					{
						if (getChildByName(iconChildren[i]) != null) 
						{
							getChildByName(iconChildren[i]).x += dx;
							getChildByName(iconChildren[i]).y += dy;
						}
					}
					break;
					
				case ABSOLUTE_COORDINATES:
					// Loop on all icons to change the x and y
					for ( i = 0; i < iconChildren.length ; i++ )
					{
						if (getChildByName(iconChildren[i]) != null)
						{
							getChildByName(iconChildren[i]).x = dx;
							getChildByName(iconChildren[i]).y = dy;
						}
					}
					break;					
			}
		}
		
		private function applyAlpha( n:Number ) :void
		{
			// Loop on all icons to change the alpha
			for ( var i:Number = 0; i < iconChildren.length ; i++ )
			{
				if (getChildByName(iconChildren[i]) != null) getChildByName(iconChildren[i]).alpha = n;
			}
		}


		/*==================================================================
			ICONFILTERS
		==================================================================*/
		
		/**
		 * The filter array applied on the upIcon of the EnhancedButton
		 */
		private var _upIconFilters:Array = new Array();
		public function get upIconFilters() :Array
		{
			return _upIconFilters;
		}
		public function set upIconFilters( a:Array ) :void
		{
			_upIconFilters = a;
			invalidateDisplayList();
		}
		
		/**
		 * The filter array applied on the downIcon of the EnhancedButton
		 */
		private var _downIconFilters:Array = new Array();
		public function get downIconFilters() :Array
		{
			return _downIconFilters;
		}
		public function set downIconFilters( a:Array ) :void
		{
			_downIconFilters = a;
			invalidateDisplayList();
		}
		
		/**
		 * The filter array applied on the overIcon of the EnhancedButton
		 */
		private var _overIconFilters:Array = new Array();
		public function get overIconFilters() :Array
		{
			return _overIconFilters;
		}
		public function set overIconFilters( a:Array ) :void
		{
			_overIconFilters = a;
			invalidateDisplayList();
		}
		
		/**
		 * The filter array applied on the upIcon of a toggled EnhancedButton (toggle = true and already clicked).
		 */
		private var _selectedUpIconFilters:Array = new Array();
		public function get selectedUpIconFilters() :Array
		{
			return _selectedUpIconFilters;
		}
		public function set selectedUpIconFilters( a:Array ) :void
		{
			_selectedUpIconFilters = a;
			invalidateDisplayList();
		}
		
		/**
		 * The filter array applied on the downIcon of a toggled EnhancedButton (toggle = true and already clicked).
		 */
		private var _selectedDownIconFilters:Array = new Array();
		public function get selectedDownIconFilters() :Array
		{
			return _selectedDownIconFilters;
		}
		public function set selectedDownIconFilters( a:Array ) :void
		{
			_selectedDownIconFilters = a;
			invalidateDisplayList();
		}
		
		/**
		 * The filter array applied on the overIcon of a toggled EnhancedButton (toggle = true and already clicked).
		 */
		private var _selectedOverIconFilters:Array = new Array();
		public function get selectedOverIconFilters() :Array
		{
			return _selectedOverIconFilters;
		}
		public function set selectedOverIconFilters( a:Array ) :void
		{
			_selectedOverIconFilters = a;
			invalidateDisplayList();
		}
		
		/**
		 * Defines the default filter applied on disabled icons
		 */
		public const DEFAULT_DISABLED_FILTER:BitmapFilter = new ColorMatrixFilter([
			0.31, 0.61, 0.08, 0, 0,
			0.31, 0.61, 0.08, 0, 0,
			0.31, 0.61, 0.08, 0, 0,
			0, 0, 0, 1, 0
			]);
			
		/**
		 * The filter array applied on the disabled/selectedDisabld Icon.
		 * 
		 * @default DEFAULT_DISABLED_FILTER
		 */
		private var _disabledIconFilters:Array = new Array(DEFAULT_DISABLED_FILTER);
		public function get disabledIconFilters() :Array
		{
			return _disabledIconFilters;
		}
		public function set disabledIconFilters( a:Array ) :void
		{
			_disabledIconFilters = a;
			invalidateDisplayList();
		}
		
		/**
		 * @private
		 * This function doesn't deal with disabled icons : managed by the enableHandler
		 */
		private function updateIconFilters() :void
		{
			if (getChildByName('upIcon') != null) getChildByName('upIcon').filters = _upIconFilters;
			if (getChildByName('downIcon') != null) getChildByName('downIcon').filters = _downIconFilters;
			if (getChildByName('overIcon') != null) getChildByName('overIcon').filters = _overIconFilters;
			if (getChildByName('selectedUpIcon') != null) getChildByName('selectedUpIcon').filters = _selectedUpIconFilters;
			if (getChildByName('selectedDownIcon') != null) getChildByName('selectedDownIcon').filters = _selectedDownIconFilters;
			if (getChildByName('selectedOverIcon') != null) getChildByName('selectedOverIcon').filters = _selectedOverIconFilters;
			if (getChildByName('disabledIcon') != null) getChildByName('disabledIcon').filters = _disabledIconFilters;
			if (getChildByName('selectedDisabledIcon') != null) getChildByName('selectedDisabledIcon').filters = _disabledIconFilters;
		}	

		/**
		 * Adds the given filter to the icon corresponding to the phase that is passed as the second argument of the function.
		 * "up" for upIcon, shown by default
		 * "over" for overIcon, shown when the mouse pass over the button...
		 * and so on
		 * 
		 * @param	newfilter 	The filter to be added
		 * @param	phase 		The phase corresponding to an icon : up, down, over, disabled, selectedUp, selectedDown, selectedOver, disabled
		 */
		public function addIconFilter( newfilter:BitmapFilter, phase:String ) :void
		{
			this["_" + phase + "IconFilters"].push( newfilter );
			invalidateDisplayList();
		}
		
		/**
		 * Removes the filter passed as argument from the filter array of the icon corresponding to the phase.
		 * Each phase corresponds to an icon.
		 * "up" for upIcon, shown by default
		 * "over" for overIcon, shown when the mouse passes over the button...
		 * and so on
		 * 
		 * @param	thisfilter 	The filter to be removed for the given phase
		 * @param	phase 		The phase corresponding to an icon : up, down, over, disabled, selectedUp, selectedDown, selectedOver, disabled
		 */
		public function removeIconFilter( thisfilter:BitmapFilter, phase:String ) :void
		{
			var target:Array = this["_" + phase + "IconFilters"]

			for (var i:int = 0 ; i < target.length ; i++ )
			{
				if ( target[i] == thisfilter )
				{
					var a:Array = target.splice( i, 1 );
				}
			}

			this["_" + phase + "IconFilters"] = target;
			invalidateDisplayList();
		}

		
		/*==================================================================
			OVERRIDES
		==================================================================*/
		
		override protected function updateDisplayList (w : Number, h : Number) :void
		{
			super.updateDisplayList(w, h);
			
			if (bStylePropChanged==true)
			{
				getCSS();
				bStylePropChanged = false; // required
			}
			
			updateIconFilters();
			moveIconBy( _iconPosX, _iconPosY );
			applyAlpha( _iconAlpha );

		}
		
		// Defines flag to indicate that a style property changed.
		private var bStylePropChanged:Boolean = true;
		
		/**
		 * Overrides the super Class method to detect changes to the new style properties.
		 * 
		 * @param styleProp:String The name of the style property, or null if all styles for this component have changed.
		 */
		override public function styleChanged (styleProp:String) :void
		{
			super.styleChanged (styleProp);

			// Check to see if a new style has changed. 
			if ( styleProp == 'iconPosX' || styleProp == 'iconPosY' || styleProp == 'iconAlpha' ||
				styleProp == 'upIconFilters' || styleProp == 'downIconFilters' || styleProp == 'overIconFilters' || styleProp == 'disabledIconFilters' ||
				styleProp == 'selectedUpIconFilters' || styleProp == 'selectedDownIconFilters' || styleProp == 'selectedOverIconFilters')
			{
				// assigns the new value of the style to the corresponding property
				this["_" + styleProp] = getStyle(styleProp);

				bStylePropChanged = true; 
				invalidateDisplayList();
				return;
			}
		}
	}
}